#pragma once

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include <pthread.h>
#include "err.hpp"
#include "RingQueue.hpp"
#include "lockGuard.hpp"
#include "Thread.hpp"

namespace ns_server
{

    const static uint16_t default_port = 8080;
    using func_t = std::function<std::string(std::string)>; // 定义了一个函数，参数是string，返回值也是string

    class UdpServer
    {
    public:
        UdpServer(/*func_t cb,*/ uint16_t port = default_port)
            : /*service_(cb),*/ port_(port)
        {
            std::cout << "server addr: " << port_ << std::endl;
            pthread_mutex_init(&lock, nullptr);

            p = new Thread(1, std::bind(&UdpServer::Recv, this));
            c = new Thread(1, std::bind(&UdpServer::Broadcast, this));
        }
        // void InitServer()
        void start()
        {
            // 1、创建socket接口，打开网络文件
            sock_ = socket(AF_INET, SOCK_DGRAM, 0); // 这个AF_INET是用来创建socket接口的
            if (sock_ < 0)
            {
                std::cerr << "create socket error: " << strerror(errno) << std::endl;
                exit(SOCKET_ERR); // 终止进程,0表示成功，非0表示失败
            }
            std::cout << "create socket error: " << sock_ << std::endl; // 3

            // 2、给服务器指明IP地址和port端口号
            struct sockaddr_in local; // 这个local在哪里定义呢？在用户空间的特点函数的栈帧上，不在内核中
            bzero(&local, sizeof(local));

            local.sin_family = AF_INET;    // 初始化sockaddr_in的结构的，也可以写成PF_INET
            local.sin_port = htons(port_); // 端口号这个字段会以报文的形式发到网络当中的，要主机转网络序列的

            // 1.字符串风格的IP地址，转换成4字节int，1.1.1.1 -> uint32_t -> 能不能强制类型转换？不能，强制是把类型改了，这里要转化
            // 2.需要将主机序列转化成为网络序列
            // inet_addr把上面两件事都做了
            // 3.云服务器，或者一款服务器，一般不需要指明某一个确定的IP
            local.sin_addr.s_addr = INADDR_ANY; // 让我们的udpserver在启动的时候，可以bind本主机上的容易IP.
            // local.sin_addr.s_addr = inet_addr(ip_.c_str()); // C风格字符串

            // int n = bind(sock_, (struct sockaddr *)&local, sizeof(local));
            if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind socket error: " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cout << "bind socket error: " << sock_ << std::endl;

            p->run();
            c->run();
        }

        void addUser(const std::string name, const struct sockaddr_in &perr)
        {
            LockGuard lockguard(&lock);
            auto iter = onlineuser.find(name);
            if (iter != onlineuser.end())
                return;
            // onlineuser.insert(std::make_pair<const std::string,const struct sockaddr_in>(name,perr));
            onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, perr));
        }
        // void Start()
        // 收,收是不断的在收
        void Recv()
        {
            char buffer[1024];
            while (true) // 服务器本质是一个死循环，随时可以使用
            {
                // 收,不断收消息
                struct sockaddr_in peer;      // 远端
                socklen_t len = sizeof(peer); // 这里一定要写清楚，未来你传入的缓冲区大小
                int n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                if (n > 0)
                    buffer[n] = '\0';
                else
                    continue;

                std::cout << "recv done.." << std::endl;

                // 提取client信息
                std::string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs(peer.sin_port); // 从网络中来的，所以要转出网络序列
                std::cout << clientip << " - " << clientport << "# " << buffer << std::endl;

                // 构建一个用户，并检查
                std::string name = clientip;
                name += "-";
                name += std::to_string(clientport);

                // 如果不存在，就插入，如果存在，就什么都不做
                addUser(name, peer);
                rq.push(buffer);

                // std::cout << "get message #" << buffer << std::endl;

                // 做业务处理
                // std::string response = service_(buffer); 对应函数excuteCommand
                // std::string message = service_(buffer); // 收到的一条消息,把消息转给所以人

                // 发，
                // 网络套接字本质也是文件，当向文件中写入时,\0并不需要写到文件中，
                // 因为\0是C语言中的规定，并不是网络的规定，网络对于客户端，也是一样,这就是为什么服务端要加\0的原因
                // sendto(sock_, buffer, strlen(buffer) /*+1,*/, 0, (struct sockaddr *)&peer, sizeof(peer)); // 谁给我发的，我转给谁

                // sendto(sock_, message.c_str(), message.size(), 0, (struct sockaddr *)&peer, sizeof(peer));
            }
        }
        // 发，对应
        void Broadcast()
        {
            while (true)//收是不断的在收，发也是，永远在环形队列里拿消息
            {
                std::string sendstring;
                rq.pop(&sendstring);

                // 给所以人发消息
                std::vector<struct sockaddr_in> v;
                {
                    LockGuard lockguard(&lock);  // 这个地方并不是最优的方式
                    for (auto user : onlineuser) // 存完以后，自己再发，后面解锁，别人可以再收
                    {
                        v.push_back(user.second); // 内存级拷贝，没调用任何系统接口
                    }
                }

                for (auto user : v)
                {
                    // std::cout<<"Broadcast message to "<<user.first<< sendstring<<std::endl;
                    sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr *)&(user), sizeof(user));

                    std::cout << "send done.." << sendstring << std::endl;
                }
            }
        }
        ~UdpServer()
        {
            pthread_mutex_destroy(&lock);
            c->join();
            p->join();
            delete c;
            delete p;
        }

    private:
        int sock_;
        uint16_t port_;
        // func_t service_; // 我们的网络服务器刚刚解决的是网络IO收发的问题，要进行业务处理

        std::unordered_map<std::string, struct sockaddr_in> onlineuser; // 在线用户
        RingQueue<std::string> rq;
        pthread_mutex_t lock;
        Thread *c;
        Thread *p;
        // std::string ip_; // 后面要专门研究一下，后面要去掉这个ip,云服务器上，不允许用户直接绑定ip地址
    };
}